﻿
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MX.Core.Encryption
{


    public class BlowfishHelper
    {
        public class BlowfishCryptographer
        {
            private bool forEncryption;
            private IBufferedCipher cipher;

            public BlowfishCryptographer(bool forEncryption, byte[] key)
            {
                this.forEncryption = forEncryption;
                BlowfishEngine engine = new BlowfishEngine();
                cipher = new PaddedBufferedBlockCipher(engine);
                var param = new KeyParameter(key);
                cipher.Init(true, param);
            }

            public byte[] DoFinal()
            {
                return cipher.DoFinal();
            }
            public byte[] DoFinal(byte[] buffer)
            {
                return cipher.DoFinal(buffer);
            }
            public byte[] DoFinal(byte[] buffer, int startIndex, int len)
            {
                return cipher.DoFinal(buffer, startIndex, len);
            }
            public byte[] ProcessBytes(byte[] buffer)
            {
                return cipher.ProcessBytes(buffer);
            }
            public byte[] ProcessBytes(byte[] buffer, int startIndex, int len)
            {
                return cipher.ProcessBytes(buffer, startIndex, len);
            }

            public int GetOutputSize(int inputLength)
            {
                return cipher.GetOutputSize(inputLength);
            }


            public void Reset()
            {
                cipher.Reset();
            }

            public int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
            {
                return cipher.ProcessBytes(input, inOff, length, output, outOff);
            }

            public int doFinal(byte[] outData, int len)
            { 
                return cipher.DoFinal(outData, len);
            }
        }

        public class KeySign
        {
            public byte[] Key { get; private set; }
            public KeySign(byte[] key)
            {
                Key = key;
            }
        }

        public class Grapher
        {
            public KeySign KeySign { get; set; }
            public BlowfishCryptographer EncodeGrapher { get; set; }
            public BlowfishCryptographer DecodeGrapher { get; set; }

        }
        static ConcurrentDictionary<KeySign, Grapher> keyMap = new ConcurrentDictionary<KeySign, Grapher>();

        private static Grapher GetGrapher(KeySign key)
        {
            var grapher = keyMap.GetOrAdd(key, (KeySign key2) =>
            {
                return new Grapher()
                {
                    DecodeGrapher = new BlowfishCryptographer(false, key2.Key),
                    EncodeGrapher = new BlowfishCryptographer(true, key2.Key),
                    KeySign = key2
                };
            });
            return grapher;
        }

        public static byte[] Encrypt(KeySign key, byte[] data)
        {
            var encodeGrapher = GetGrapher(key).EncodeGrapher;
            int outputSize = encodeGrapher.GetOutputSize(data.Length);
            byte[] outData = new byte[outputSize];
            int len = encodeGrapher.ProcessBytes(data, 0, data.Length, outData, 0);
            encodeGrapher.doFinal(outData, len);
            Array.Resize(ref outData, len);
            return outData;
        }

        public static byte[] Decrypt(KeySign key, byte[] data)
        {
            var decodeGrapher = GetGrapher(key).DecodeGrapher;
            int outputSize = decodeGrapher.GetOutputSize(data.Length);
            byte[] outData = new byte[outputSize];
            int len = decodeGrapher.ProcessBytes(data, 0, data.Length, outData, 0);
            var len2 = decodeGrapher.doFinal(outData, len);
            Array.Resize(ref outData, len);
            return outData;
        }

    }
}
